Skip to main content

Johnson's Algorithm

1. What is Johnson's Algorithm?​

Johnson's Algorithm is used to find the shortest paths between all pairs of vertices in a weighted, directed graph. It combines both Dijkstra's and the Bellman-Ford algorithm to efficiently handle graphs with both positive and negative edge weights, provided there are no negative weight cycles.

2. Algorithm for Johnson's Algorithm​

  1. Add a New Vertex: Add a new vertex to the graph and connect it to every other vertex with an edge of weight 0.
  2. Bellman-Ford Algorithm: Use the Bellman-Ford algorithm to find the shortest path from the new vertex to every other vertex. If a negative weight cycle is detected, the algorithm terminates.
  3. Reweighting: Reweight all edges in the original graph to ensure all edges have non-negative weights.
  4. Dijkstra's Algorithm: Use Dijkstra's algorithm to find the shortest paths between all pairs of vertices in the reweighted graph.
  5. Adjust Weights: Adjust the weights of the paths found by removing the effects of reweighting.

3. How Does Johnson's Algorithm Work?​

  • Bellman-Ford Algorithm: Used to find shortest paths from the new vertex, detecting any negative weight cycles.
  • Reweighting: Transforms the edge weights to non-negative, allowing Dijkstra's algorithm to be used.
  • Dijkstra's Algorithm: Finds shortest paths between all pairs of vertices in the reweighted graph.
  • Adjusting Weights: Converts the shortest paths in the reweighted graph back to the original weights.

4. Problem Description​

Given a weighted, directed graph, find the shortest paths between all pairs of vertices, ensuring the graph has no negative weight cycles.

5. Examples​

Example graph:

      4 β†’ 5
↑ ↓
1 β†’ 3 β†’ 2
↓ ↓
0 ← 1

Shortest Path Matrix:

  0  1  2  3  4  5
0[0, 1, 2, 1, 3, 3]
1[1, 0, 1, 2, 2, 2]
2[2, 1, 0, 1, 1, 1]
3[1, 2, 1, 0, 2, 2]
4[3, 2, 1, 2, 0, 1]
5[3, 2, 1, 2, 1, 0]

6. Constraints​

  • The graph must not contain any negative weight cycles.

7. Implementation​

Johnson's Algorithm​

import heapq

def bellman_ford(graph, source):
distance = {v: float('inf') for v in graph}
distance[source] = 0

for _ in range(len(graph) - 1):
for u in graph:
for v, weight in graph[u]:
if distance[u] + weight < distance[v]:
distance[v] = distance[u] + weight

for u in graph:
for v, weight in graph[u]:
if distance[u] + weight < distance[v]:
return None # Negative weight cycle detected

return distance

def dijkstra(graph, source):
pq = [(0, source)]
distance = {v: float('inf') for v in graph}
distance[source] = 0

while pq:
dist, u = heapq.heappop(pq)
if dist > distance[u]:
continue
for v, weight in graph[u]:
if distance[u] + weight < distance[v]:
distance[v] = distance[u] + weight
heapq.heappush(pq, (distance[v], v))

return distance

def johnson(graph):
new_vertex = 'q'
graph[new_vertex] = [(v, 0) for v in graph]

h = bellman_ford(graph, new_vertex)
if h is None:
return None # Negative weight cycle detected

del graph[new_vertex]
reweighted_graph = {}
for u in graph:
reweighted_graph[u] = []
for v, weight in graph[u]:
reweighted_graph[u].append((v, weight + h[u] - h[v]))

distance = {}
for u in graph:
distance[u] = dijkstra(reweighted_graph, u)
for v in distance[u]:
distance[u][v] += h[v] - h[u]

return distance

graph = {
0: [(1, 1), (3, 1)],
1: [(2, 1)],
2: [(4, 1)],
3: [(2, 1)],
4: [(0, 1), (5, 1)],
5: [(2, 1)]
}

print(johnson(graph))

8. Complexity Analysis​

  • Time Complexity: O(V2log⁑V+VE)O(V^2 \log V + VE) where VV is the number of vertices and EE is the number of edges.
  • Space Complexity: O(V2)O(V^2) due to the distance matrix and reweighted graph.

9. Advantages and Disadvantages​

Advantages:​

  • Handles graphs with negative weights.
  • Efficient for sparse graphs.

Disadvantages:​

  • Cannot handle graphs with negative weight cycles.
  • More complex to implement compared to other shortest path algorithms.

10. References​